package cn.iocoder.yudao.module.pay.service.order;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.extra.spring.SpringUtil;
import cn.iocoder.yudao.framework.common.enums.UserTypeEnum;
import cn.iocoder.yudao.framework.common.enums.pay.PayCollectOrderTypeEnum;
import cn.iocoder.yudao.framework.common.enums.pay.PayOrderStatusEnum;
import cn.iocoder.yudao.framework.common.enums.pay.PayWalletTypeEnum;
import cn.iocoder.yudao.framework.common.enums.pay.PayorderDetailStatusEnum;
import cn.iocoder.yudao.framework.common.enums.trade.TransactionTypeEnum;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
import cn.iocoder.yudao.framework.pay.core.client.PayClient;
import cn.iocoder.yudao.framework.pay.core.client.dto.order.PayOrderRespDTO;
import cn.iocoder.yudao.framework.pay.core.client.dto.order.PayOrderUnifiedReqDTO;
import cn.iocoder.yudao.framework.pay.core.enums.channel.PayChannelEnum;
import cn.iocoder.yudao.framework.pay.core.enums.order.PayOrderStatusRespEnum;
import cn.iocoder.yudao.framework.tenant.core.util.TenantUtils;
import cn.iocoder.yudao.module.pay.api.order.dto.PayOrderCreateReqDTO;
import cn.iocoder.yudao.module.pay.controller.admin.order.vo.PayOrderExportReqVO;
import cn.iocoder.yudao.module.pay.controller.admin.order.vo.PayOrderPageReqVO;
import cn.iocoder.yudao.module.pay.controller.admin.order.vo.PayOrderSubmitReqVO;
import cn.iocoder.yudao.module.pay.controller.admin.order.vo.PayOrderSubmitRespVO;
import cn.iocoder.yudao.module.pay.convert.order.PayOrderConvert;
import cn.iocoder.yudao.module.pay.dal.dataobject.app.PayAppDO;
import cn.iocoder.yudao.module.pay.dal.dataobject.channel.PayChannelDO;
import cn.iocoder.yudao.module.pay.dal.dataobject.order.PayOrderDO;
import cn.iocoder.yudao.module.pay.dal.dataobject.order.PayOrderDetailDO;
import cn.iocoder.yudao.module.pay.dal.dataobject.paycollectorder.PayCollectOrderDO;
import cn.iocoder.yudao.module.pay.dal.mysql.order.PayOrderDetailMapper;
import cn.iocoder.yudao.module.pay.dal.mysql.order.PayOrderExtensionMapper;
import cn.iocoder.yudao.module.pay.dal.mysql.order.PayOrderMapper;
import cn.iocoder.yudao.module.pay.dal.redis.no.PayNoRedisDAO;
import cn.iocoder.yudao.module.pay.enums.notify.PayNotifyTypeEnum;
import cn.iocoder.yudao.module.pay.framework.pay.config.PayProperties;
import cn.iocoder.yudao.module.pay.framework.pay.core.WalletPayClient;
import cn.iocoder.yudao.module.pay.service.app.PayAppService;
import cn.iocoder.yudao.module.pay.service.channel.PayChannelService;
import cn.iocoder.yudao.module.pay.service.notify.PayNotifyService;
import cn.iocoder.yudao.module.pay.service.paycollectorder.PayCollectOrderService;
import cn.iocoder.yudao.module.pay.service.wallet.PayWalletService;
import com.google.common.collect.Maps;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.validation.annotation.Validated;

import java.math.BigDecimal;
import java.time.Duration;
import java.time.LocalDateTime;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;

import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
import static cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils.addTime;
import static cn.iocoder.yudao.framework.common.util.json.JsonUtils.toJsonString;
import static cn.iocoder.yudao.framework.common.util.servlet.ServletUtils.getClientIP;
import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserName;
import static cn.iocoder.yudao.module.pay.enums.ErrorCodeConstants.*;

/**
 * 支付订单 Service 实现类
 *
 * @author aquan
 */
@Service
@Validated
@Slf4j
public class PayOrderServiceImpl implements PayOrderService {

    @Resource
    private PayProperties payProperties;

    @Resource
    private PayOrderMapper orderMapper;
    @Resource
    private PayOrderExtensionMapper orderExtensionMapper;
    @Resource
    private PayOrderDetailMapper orderDetailMapper;
    @Resource
    private PayNoRedisDAO noRedisDAO;

    @Resource
    private PayAppService appService;
    @Resource
    private PayChannelService channelService;
    @Resource
    private PayNotifyService notifyService;
    @Resource
    private PayWalletService walletService;
    @Resource
    private PayCollectOrderService collectOrderService;

    @Override
    public PayOrderDO getOrder(Long id) {
        return orderMapper.selectById(id);
    }

    @Override
    public PayOrderDO getOrder(Long appId, String merchantOrderId) {
        return orderMapper.selectByAppIdAndOrderNo(appId, merchantOrderId);
    }

    @Override
    public List<PayOrderDO> getOrderList(Collection<Long> ids) {
        if (CollUtil.isEmpty(ids)) {
            return Collections.emptyList();
        }
        return orderMapper.selectBatchIds(ids);
    }

    @Override
    public Long getOrderCountByAppId(Long appId) {
        return orderMapper.selectCountByAppId(appId);
    }

    @Override
    public PageResult<PayOrderDO> getOrderPage(PayOrderPageReqVO pageReqVO) {
        return orderMapper.selectPage(pageReqVO);
    }

    @Override
    public List<PayOrderDO> getOrderList(PayOrderExportReqVO exportReqVO) {
        return orderMapper.selectList(exportReqVO);
    }

    @Override
    public Long createOrder(PayOrderCreateReqDTO reqDTO) {
        // 校验 App
        PayAppDO app = appService.validPayApp(reqDTO.getAppKey());

        // 查询对应的支付交易单是否已经存在。如果是，则直接返回
        PayOrderDO order = orderMapper.selectByAppIdAndOrderNo(app.getId(), reqDTO.getOrderNo());
        PayNotifyTypeEnum payType = PayNotifyTypeEnum.getEnum(reqDTO.getPayType());

        PayOrderDetailDO payDetail = new PayOrderDetailDO();
        StringBuilder payName = new StringBuilder();
        payName.append(TransactionTypeEnum.getEnum(reqDTO.getOrderType()).getDesc()).append("-").append(payType.getName());
        if (order == null) {
            // 创建支付交易单
            order = PayOrderDO.builder()
                    .orderNo(reqDTO.getOrderNo())
                    .weight(reqDTO.getWeight())
                    .exchangeWeight(reqDTO.getExchangeWeight())
                    .metalPrice(reqDTO.getMetalPrice())
                    .appId(app.getId())
                    .subject(payName.toString())
                    .notifyUrl(app.getOrderNotifyUrl())
                    // 订单相关字段
                    .status(PayOrderStatusEnum.WAITING.getStatus())
                    // 退款相关字段
                    .refundPrice(BigDecimal.ZERO)
                    .build();
            orderMapper.insert(order);
        }
        if (PayNotifyTypeEnum.isSettle(reqDTO.getPayType())) {
            // 转换为JSON对象并设置到payDetail
            payDetail.setExtendItem(getExtendPrice(reqDTO));
        }
//        order = orderMapper.selectById(order.getId());
        payDetail.setPayId(order.getId());
        payDetail.setOrderNo(reqDTO.getOrderNo());
        payDetail.setPayType(reqDTO.getPayType());

//        payDetail.setStatus();
//        payDetail.setExpireTime();
        payDetail.setPrice(reqDTO.getPrice());
        payDetail.setPayPrice(reqDTO.getPrice());
        order.setWaitingPay(payDetail.getPayPrice());

        payDetail.setPayId(order.getId());
        orderDetailMapper.insertDetail(payDetail);
        orderMapper.updateById(order);
        // TODO: 2025/4/4 自动支付,记录日志
        if (reqDTO.getAutoPay() != null && reqDTO.getAutoPay()) {
            this.autoPay(reqDTO.getChannelCode(),order.getId(), payDetail.getId(), app.getOrderNotifyUrl(), reqDTO);
        }
        return order.getId();
    }

    @Override // 注意，这里不能添加事务注解，避免调用支付渠道失败时，将 PayOrderExtensionDO 回滚了
    public PayOrderSubmitRespVO submitOrder(PayOrderSubmitReqVO reqVO, String userIp) {
        // 1.1 获得 PayOrderDO ，并校验其是否存在
        PayOrderDetailDO pay = validateOrderDetailCanSubmit(reqVO.getId());
        PayOrderDO order = validateOrderCanSubmit(pay.getPayId());
        // 1.32 校验支付渠道是否有效
        PayChannelDO channel = validateChannelCanSubmit(order.getAppId(), reqVO.getChannelCode());
        PayClient client = channelService.getPayClient(channel.getId());

        // 2.
        String no = noRedisDAO.generate(payProperties.getOrderNoPrefix());
        pay.setNo(no).setChannelId(channel.getId());
        orderDetailMapper.updateById(pay);

        // 3. 调用三方接口
        PayOrderUnifiedReqDTO unifiedOrderReqDTO = PayOrderConvert.INSTANCE.convert2(reqVO, userIp)
                // 商户相关的字段
                .setOutTradeNo(no) // 注意，此处使用的是 PayOrderExtensionDO.no 属性！
//                .setSubject(order.getSubject()).setBody(order.getBody())
                .setNotifyUrl(genChannelOrderNotifyUrl(channel))
                .setReturnUrl(reqVO.getReturnUrl())
                // 订单相关字段
                .setPrice(pay.getPayPrice()).setExpireTime(pay.getExpireTime()).setPayType(pay.getPayType())
                .setWalletType(reqVO.getWalletType());
        PayOrderRespDTO unifiedOrderResp = client. unifiedOrder(unifiedOrderReqDTO);

        // 4. 如果调用直接支付成功，则直接更新支付单状态为成功。例如说：付款码支付，免密支付时，就直接验证支付成功
        if (unifiedOrderResp != null) {
            try {
                getSelf().notifyOrder(channel, unifiedOrderResp);
            } catch (Exception e) {
                // 兼容 https://gitee.com/zhijiantianya/yudao-cloud/issues/I8SM9H 场景
                // 支付宝或微信扫码之后时，由于 PayClient 是直接返回支付成功，而支付也会有回调，导致存在并发更新问题，此时一般是可以 try catch 直接忽略
                log.warn("[submitOrder][order({}) channel({}) 支付结果({}) 通知时发生异常，可能是并发问题]",
                        order, channel, unifiedOrderResp, e);
            }
            // 如有渠道错误码，则抛出业务异常，提示用户
            if (StrUtil.isNotEmpty(unifiedOrderResp.getChannelErrorCode())) {
                throw exception(PAY_ORDER_SUBMIT_CHANNEL_ERROR, unifiedOrderResp.getChannelErrorCode(),
                        unifiedOrderResp.getChannelErrorMsg());
            }
            // 此处需要读取最新的状态
            pay = orderDetailMapper.selectById(pay.getId());
        }
        return PayOrderConvert.INSTANCE.convert(pay, unifiedOrderResp);
    }

    private PayOrderDetailDO validateOrderDetailCanSubmit(Long id) {
        PayOrderDetailDO pay = orderDetailMapper.selectById(id);
        if (pay == null) { // 是否存在
            throw exception(PAY_ORDER_NOT_FOUND);
        }
        if (PayOrderStatusEnum.isClear(pay.getStatus())) { // 校验状态，发现已支付
            throw exception(PAY_ORDER_STATUS_IS_SUCCESS);
        }
        if (!PayOrderStatusEnum.WAITING.getStatus().equals(pay.getStatus())) { // 校验状态，必须是待支付
            throw exception(PAY_ORDER_STATUS_IS_NOT_WAITING);
        }
//        if (LocalDateTimeUtils.beforeNow(pay.getExpireTime())) { // 校验是否过期
//            throw exception(PAY_ORDER_IS_EXPIRED);
//        }
        // 【重要】校验是否支付拓展单已支付，只是没有回调、或者数据不正常
//        validateOrderActuallyPaid(id);
        return pay;
    }

    private PayOrderDO validateOrderCanSubmit(Long id) {
        PayOrderDO order = orderMapper.selectById(id);
        if (order == null) { // 是否存在
            throw exception(PAY_ORDER_NOT_FOUND);
        }
        if (PayOrderStatusEnum.isOrderSuccess(order.getStatus())) { // 校验状态，发现已支付
            throw exception(PAY_ORDER_STATUS_IS_SUCCESS);
        }
        // 【重要】校验是否支付拓展单已支付，只是没有回调、或者数据不正常
//        validateOrderActuallyPaid(id);
        return order;
    }

    /**
     * 校验支付订单实际已支付
     *
     */
//    @VisibleForTesting
//    void validateOrderActuallyPaid(Long id) {
//        List<PayOrderExtensionDO> orderExtensions = orderExtensionMapper.selectListByOrderId(id);
//        orderExtensions.forEach(orderExtension -> {
//            // 情况一：校验数据库中的 orderExtension 是不是已支付
//            if (PayOrderStatusEnum.isOrderSuccess(orderExtension.getStatus())) {
//                log.warn("[validateOrderCanSubmit][order({}) 的 extension({}) 已支付，可能是数据不一致]",
//                        id, orderExtension.getId());
//                throw exception(PAY_ORDER_EXTENSION_IS_PAID);
//            }
//            // 情况二：调用三方接口，查询支付单状态，是不是已支付
//            PayClient payClient = channelService.getPayClient(orderExtension.getChannelId());
//            if (payClient == null) {
//                log.error("[validateOrderCanSubmit][渠道编号({}) 找不到对应的支付客户端]", orderExtension.getChannelId());
//                return;
//            }
//            PayOrderRespDTO respDTO = payClient.getOrder(orderExtension.getNo());
//            if (respDTO != null && PayOrderStatusRespEnum.isSuccess(respDTO.getStatus())) {
//                log.warn("[validateOrderCanSubmit][order({}) 的 PayOrderRespDTO({}) 已支付，可能是回调延迟]",
//                        id, toJsonString(respDTO));
//                throw exception(PAY_ORDER_EXTENSION_IS_PAID);
//            }
//        });
//    }

    private PayChannelDO validateChannelCanSubmit(Long appId, String channelCode) {
        // 校验 App
        appService.validPayApp(appId);
        // 校验支付渠道是否有效
        PayChannelDO channel = channelService.validPayChannel(appId, channelCode);
        PayClient client = channelService.getPayClient(channel.getId());
        if (client == null) {
            log.error("[validatePayChannelCanSubmit][渠道编号({}) 找不到对应的支付客户端]", channel.getId());
            throw exception(CHANNEL_NOT_FOUND);
        }
        return channel;
    }

    /**
     * 根据支付渠道的编码，生成支付渠道的回调地址
     *
     * @param channel 支付渠道
     * @return 支付渠道的回调地址  配置地址 + "/" + channel id
     */
    private String genChannelOrderNotifyUrl(PayChannelDO channel) {
        return payProperties.getOrderNotifyUrl() + "/" + channel.getId();
    }

    @Override
    public void notifyOrder(Long channelId, PayOrderRespDTO notify) {
        // 校验支付渠道是否有效
        PayChannelDO channel = channelService.validPayChannel(channelId);
        // 更新支付订单为已支付
        TenantUtils.execute(channel.getTenantId(), () -> getSelf().notifyOrder(channel, notify));
    }

    /**
     * 通知并更新订单的支付结果
     *
     * @param channel 支付渠道
     * @param notify  通知
     */
    @Transactional(rollbackFor = Exception.class)
    // 注意，如果是方法内调用该方法，需要通过 getSelf().notifyPayOrder(channel, notify) 调用，否则事务不生效
    public void notifyOrder(PayChannelDO channel, PayOrderRespDTO notify) {
        // 情况一：支付成功的回调
        if (PayOrderStatusRespEnum.isSuccess(notify.getStatus())) {
            notifyOrderSuccess(channel, notify);
            return;
        }
        // 情况二：支付失败的回调
        if (PayOrderStatusRespEnum.isClosed(notify.getStatus())) {
            notifyOrderClosed(channel, notify);
        }
        // 情况三：WAITING：无需处理
        // 情况四：REFUND：通过退款回调处理
    }

    private void notifyOrderSuccess(PayChannelDO channel, PayOrderRespDTO notify) {
        // 1. 更新 PayOrderExtensionDO 支付成功
        PayOrderDetailDO pay = updateOrderPaid(notify);

        //  更新 PayOrderDO 已结算/部分支付
        Boolean paid = updateOrderPaid(channel, pay, notify);
        if (paid) { // 如果之前已经成功回调，则直接返回，不用重复记录支付通知记录；例如说：支付平台重复回调
            return;
        }
        // 插入支付通知记录
        notifyService.createPayNotifyTask(notify.getPayType(), pay.getPayId());
    }

    /**
     * 更新 PayOrderExtensionDO 支付成功
     *
     * @param notify 通知
     * @return PayOrderExtensionDO 对象
     */
    private PayOrderDetailDO updateOrderPaid(PayOrderRespDTO notify) {
        // 1. 查询 PayOrderExtensionDO
        PayOrderDetailDO pay = orderDetailMapper.selectByNo(notify.getOutTradeNo());
        if (pay == null) {
            throw exception(PAY_ORDER_DETAIL_NOT_FOUND);
        }
        if (PayorderDetailStatusEnum.SUCCESS.getStatus().equals(pay.getStatus())) { // 如果已经是成功，直接返回，不用重复更新
            log.info("[updateOrderExtensionSuccess][orderExtension({}) 已经是已支付，无需更新]", pay.getId());
            return pay;
        }
        if (ObjectUtil.notEqual(pay.getStatus(), PayorderDetailStatusEnum.WAITING.getStatus())) { // 校验状态，必须是待支付
            throw exception(PAY_ORDER_DETAIL_STATUS_IS_NOT_WAITING);
        }

        pay.setSuccessTime(LocalDateTime.now()).setChannelNotifyData(toJsonString(notify));

        if (PayOrderStatusRespEnum.PART_SUCCESS.getStatus().equals(notify.getStatus())) {
            pay.setStatus(PayorderDetailStatusEnum.PART_SUCCESS.getStatus());
        } else {
            pay.setStatus(PayorderDetailStatusEnum.SUCCESS.getStatus());
        }
        // 2. 更新
        int updateCounts = orderDetailMapper.updateById(pay);
        if (updateCounts == 0) { // 校验状态，必须是待支付
            throw exception(PAY_ORDER_DETAIL_STATUS_IS_NOT_WAITING);
        }
        log.info("[updateOrderDetailSuccess][orderDetail({}) 更新为已支付]", pay.getId());
        return pay;
    }

    /**
     * 更新 PayOrderDO 支付成功
     *
     * @param channel        支付渠道
     * @param pay 支付拓展单
     * @param notify         通知回调
     * @return 是否之前已经成功回调
     */
    private Boolean updateOrderPaid(PayChannelDO channel, PayOrderDetailDO pay,
                                    PayOrderRespDTO notify) {
        // 1. 判断 PayOrderDO 是否处于待支付
        PayOrderDO order = orderMapper.selectById(pay.getPayId());
        if (order == null) {
            throw exception(PAY_ORDER_NOT_FOUND);
        }
        if (PayOrderStatusEnum.isOrderSuccess(order.getStatus())) {
            log.info("[updateOrderSuccess][order({}) 已经是已结算，无需更新]", order.getId());
            return true;
        }
        // 2. 更新 PayOrderDO
        setExtendPrice(pay, order);
        order.setTotalPrice(order.getTotalPrice().add(pay.getPayPrice()));
        order.setWaitingPay(order.getWaitingPay().subtract(pay.getPayPrice()));
        order.setStatus(PayOrderStatusEnum.PART_PAY.getStatus());
        order.setChannelId(channel.getId());
        order.setChannelCode(channel.getCode());

        PayOrderStatusEnum statusEnum = PayOrderStatusEnum.valueOf(order.getStatus());
        // 结算单成功支付 -> 订单结清
        if (PayNotifyTypeEnum.isSettle(pay.getPayType())) {
            if (PayorderDetailStatusEnum.isOrderSuccess(pay.getStatus())) {
                statusEnum = PayOrderStatusEnum.CLEAR;
            } else if (PayorderDetailStatusEnum.PART_SUCCESS.getStatus().equals(pay.getStatus())){
                statusEnum = PayOrderStatusEnum.UNCLEAR;
            }
            order.setStatus(statusEnum.getStatus());
        }

        int updateCounts = orderMapper.updateById(order);
        log.info("[updateOrderExtensionSuccess][order({}) 更新为{}]", order.getId(), statusEnum.getName());
        return false;
    }

    private void notifyOrderClosed(PayChannelDO channel, PayOrderRespDTO notify) {
        updateOrderDetailFail(channel, notify);
    }

    private void updateOrderDetailFail(PayChannelDO channel, PayOrderRespDTO notify) {
        // 1. 查询 PayOrderExtensionDO
        PayOrderDetailDO payDetail = orderDetailMapper.selectByNo(notify.getOutTradeNo());
        if (payDetail == null) {
            throw exception(PAY_ORDER_DETAIL_NOT_FOUND);
        }
        if (PayorderDetailStatusEnum.isClosed(payDetail.getStatus())) { // 如果已经是关闭，直接返回，不用重复更新
            log.info("[updateOrderDetailClosed][orderDetail({}) 已经是支付关闭，无需更新]", payDetail.getId());
            return;
        }
        if (ObjectUtil.notEqual(payDetail.getStatus(), PayorderDetailStatusEnum.WAITING.getStatus())) { // 校验状态，必须是待支付
            throw exception(PAY_ORDER_DETAIL_STATUS_IS_NOT_WAITING);
        }

        // 2. 更新 PayOrderDetailDO
        int updateCounts = orderDetailMapper.updateByIdAndStatus(payDetail.getId(), payDetail.getStatus(),
                PayOrderDetailDO.builder().status(PayorderDetailStatusEnum.FAIL.getStatus())
                        .channelNotifyData(toJsonString(notify)).build());
        if (updateCounts == 0) { // 校验状态，必须是待支付
            throw exception(PAY_ORDER_DETAIL_STATUS_IS_NOT_WAITING);
        }
        log.info("[updateOrderDetailClosed][orderDetail({}) 更新为支付失败]", payDetail.getId());
    }

    @Override
    public void updateOrderRefundPrice(Long id, Integer incrRefundPrice) {
//        PayOrderDO order = orderMapper.selectById(id);
//        if (order == null) {
//            throw exception(PAY_ORDER_NOT_FOUND);
//        }
//        if (!PayOrderStatusEnum.isSuccessOrRefund(order.getStatus())) {
//            throw exception(PAY_ORDER_REFUND_FAIL_STATUS_ERROR);
//        }
//        if (order.getRefundPrice() + incrRefundPrice > order.getPrice()) {
//            throw exception(REFUND_PRICE_EXCEED);
//        }
//
//        // 更新订单
//        PayOrderDO updateObj = new PayOrderDO()
//                .setRefundPrice(order.getRefundPrice() + incrRefundPrice)
//                .setStatus(PayOrderStatusEnum.REFUND.getStatus());
//        int updateCount = orderMapper.updateByIdAndStatus(id, order.getStatus(), updateObj);
//        if (updateCount == 0) {
//            throw exception(PAY_ORDER_REFUND_FAIL_STATUS_ERROR);
//        }
    }

    @Override
    public void updatePayOrderPrice(Long id, Integer payPrice) {
//        PayOrderDO order = orderMapper.selectById(id);
//        if (order == null) {
//            throw exception(PAY_ORDER_NOT_FOUND);
//        }
//        if (ObjectUtil.notEqual(PayOrderStatusEnum.WAITING.getStatus(), order.getStatus())) {
//            throw exception(PAY_ORDER_STATUS_IS_NOT_WAITING);
//        }
//        if (ObjectUtil.equal(order.getPrice(), payPrice)) {
//            return;
//        }
//
//        orderMapper.updateById(new PayOrderDO().setId(order.getId()).setPrice(payPrice));
    }

    @Override
    public int syncOrder(LocalDateTime minCreateTime) {
        // 1. 查询指定创建时间前的待支付订单
        List<PayOrderDetailDO> orderDetails = orderDetailMapper.selectListByStatusAndCreateTimeGe(
                PayOrderStatusEnum.WAITING.getStatus(), minCreateTime);
        if (CollUtil.isEmpty(orderDetails)) {
            return 0;
        }
        // 2. 遍历执行
        int count = 0;
        for (PayOrderDetailDO orderDetail : orderDetails) {
            count += syncOrder(orderDetail) ? 1 : 0;
        }
        return count;
    }

    @Override
    public void syncOrderQuietly(Long id) {
        // 1. 查询待支付订单
        List<PayOrderDetailDO> orderDetails = orderDetailMapper.selectListByOrderIdAndStatus(id,
                PayOrderStatusEnum.WAITING.getStatus());

        // 2. 遍历执行
        for (PayOrderDetailDO orderDetail : orderDetails) {
            syncOrder(orderDetail);
        }
    }

    /**
     * 同步单个支付拓展单
     *
     * @param orderDetailDO 支付详情单
     * @return 是否已支付
     */
    private boolean syncOrder(PayOrderDetailDO orderDetailDO) {
        try {
            // 1.1 查询支付订单信息
            PayClient payClient = channelService.getPayClient(orderDetailDO.getChannelId());
            if (payClient == null) {
                log.error("[syncOrder][渠道编号({}) 找不到对应的支付客户端]", orderDetailDO.getChannelId());
                return false;
            }
            PayOrderRespDTO respDTO = payClient.getOrder(orderDetailDO.getNo());
            // 如果查询到订单不存在，PayClient 返回的状态为关闭。但此时不能关闭订单。存在以下一种场景：
            //  拉起渠道支付后，短时间内用户未及时完成支付，但是该订单同步定时任务恰巧自动触发了，主动查询结果为订单不存在。
            //  当用户支付成功之后，该订单状态在渠道的回调中无法从已关闭改为已支付，造成重大影响。
            // 考虑此定时任务是异常场景的兜底操作，因此这里不做变更，优先以回调为准。
            // 让订单自动随着支付渠道那边一起等到过期，确保渠道先过期关闭支付入口，而后通过订单过期定时任务关闭自己的订单。
            if (PayOrderStatusRespEnum.isClosed(respDTO.getStatus())) {
                return false;
            }
            // 1.2 回调支付结果
            notifyOrder(orderDetailDO.getChannelId(), respDTO);

            // 2. 如果是已支付，则返回 true
            return PayOrderStatusRespEnum.isSuccess(respDTO.getStatus());
        } catch (Throwable e) {
            log.error("[syncOrder][orderExtension({}) 同步支付状态异常]", orderDetailDO.getId(), e);
            return false;
        }
    }

    // TODO: 2025/3/23 订单过期支付过期，按类型 状态
    // 全款买：2min 待支付
    // 锁价买卖：待支付 -> 已经支付定金
    // 旧料兑换：待支付 -> ?
    @Override
    public int expireOrder() {
        // 1. 查询过期的待支付订单
        List<PayOrderDO> orders = orderMapper.selectListByStatusAndExpireTimeLt(
                PayOrderStatusEnum.WAITING.getStatus(), LocalDateTime.now());
        if (CollUtil.isEmpty(orders)) {
            return 0;
        }

        // 2. 遍历执行
        int count = 0;
        for (PayOrderDO order : orders) {
            count += expireOrder(order) ? 1 : 0;
        }
        return count;
    }

    @Override
    public int expireOrderDetail() {
        // 1. 查询过期的待支付订单
        List<PayOrderDetailDO> orders = orderDetailMapper.selectListByStatusAndExpireTimeLt(
                PayOrderStatusEnum.WAITING.getStatus(), LocalDateTime.now());
        if (CollUtil.isEmpty(orders)) {
            return 0;
        }

        // 2. 遍历执行
        int count = 0;
        for (PayOrderDetailDO order : orders) {
            try {
                count += expireOrderDetail(order) ? 1 : 0;
            } catch (Exception e) {
                log.error("[expireOrderDetail][order({}) 过期订单异常]", order.getId(), e);
            }
        }
        return count;
    }

    @Transactional(rollbackFor = Exception.class)
    public boolean expireOrderDetail(PayOrderDetailDO order) {
        PayOrderDetailDO updateObj = new PayOrderDetailDO().setStatus(PayorderDetailStatusEnum.CLOSED.getStatus());
        if (orderDetailMapper.updateByIdAndStatus(order.getId(), order.getStatus(), updateObj) == 0) {
            log.error("[expireOrderDetail][order({}) 更新为支付关闭失败]", order.getId());
            throw exception(PAY_ORDER_DETAIL_CLOSED_FAILED);
        }
        log.info("[expireOrderDetail][order({}) 更新为支付关闭成功]", order.getId());
        
        PayOrderDO updateOrder = new PayOrderDO().setStatus(PayOrderStatusEnum.CLOSED.getStatus())
                .setWaitingPay(BigDecimal.ZERO);
        if (orderMapper.updateByIdAndStatus(order.getPayId(), PayOrderStatusEnum.CLOSED.getStatus(), updateOrder) == 0) {
            log.error("[expireOrder][order({}) 更新为支付关闭失败]", order.getId());
            throw exception(PAY_ORDER_CLOSED_FAILED);
        }

        log.info("[expireOrder][order({}) 更新为支付关闭成功]", order.getId());
        return true;
    }

    @Override
    public PayOrderDetailDO getOrderDetailByNo(String outTradeNo) {
        return orderDetailMapper.selectOne(PayOrderDetailDO::getNo, outTradeNo);
    }

    @Override
    public List<PayOrderDetailDO> getOrderDetailList(Long id) {
        return orderDetailMapper.selectList(PayOrderDetailDO::getPayId, id);
    }

    @Override
    public PayOrderDetailDO getOrderDetail(Long id) {
        return orderDetailMapper.selectOne(PayOrderDetailDO::getId, id);
    }

    @Override
    public PayOrderDetailDO getWaitingPayByPayOrder(Long id) {
        return orderDetailMapper.selectOne(PayOrderDetailDO::getPayId, id,
                PayOrderDetailDO::getStatus, PayorderDetailStatusEnum.WAITING.getStatus());
    }

    /**
     * 钱包支付余额小于 0 实际支付 = 应支付 + 欠款(负)
     * @param outTradeNo
     * @param balance
     */
    @Override
    public void updatePayOrderDetailPrice(String outTradeNo, BigDecimal balance) {
        PayOrderDetailDO detailDO = getOrderDetailByNo(outTradeNo);
        if (detailDO != null) {
            if (balance.compareTo(BigDecimal.ZERO) < 0) {
                detailDO.setPayPrice(detailDO.getPrice().add(balance));
            } else {
                detailDO.setPayPrice(detailDO.getPrice());
            }
            orderDetailMapper.updateById(detailDO);
        }
    }

    @Override
    public void refundDeposit(Long payOrderId, Long userId) {
        PayOrderDO payOrderDO = orderMapper.selectById(payOrderId);
        BigDecimal deposit = payOrderDO.getDeposit();
        if (deposit.compareTo(BigDecimal.ZERO) <= 0) {
            return;
        }
        payOrderDO.setDeposit(BigDecimal.ZERO);
        payOrderDO.setDepositAdd(BigDecimal.ZERO);
        orderMapper.updateById(payOrderDO);
        walletService.refundDeposit(userId, deposit, payOrderId);
    }

    @Override
    public void createSubmitByPayOrder(Long orderId, List<String> payPicUrl) {
        PayOrderSubmitReqVO reqVO = new PayOrderSubmitReqVO();
        PayOrderDO payOrder = getOrder(orderId);
        PayOrderDetailDO payDetail = getWaitingPayByPayOrder(orderId);
        reqVO.setId(payDetail.getId());
        reqVO.setPayOrderId(payOrder.getId());
        reqVO.setChannelCode(PayChannelEnum.SPEC.getCode());
        reqVO.setReturnUrl(payOrder.getNotifyUrl());
        reqVO.setCheckResult(true);
        reqVO.setPayPicUrl(payPicUrl);
        Map<String, String> channelExtras = Maps.newHashMapWithExpectedSize(2);
        channelExtras.put(WalletPayClient.USER_ID_KEY, String.valueOf(getLoginUserId()));
        channelExtras.put(WalletPayClient.USER_TYPE_KEY, String.valueOf(UserTypeEnum.MEMBER_PERSON.getValue()));
        reqVO.setChannelExtras(channelExtras);
        submitOrder(reqVO, getClientIP());
    }

    @Override
    public PayOrderDO getOrderByOrderNo(String orderNo) {
        return orderMapper.selectOne(PayOrderDO::getOrderNo, orderNo);
    }

    @Override
    public void updatePayOrderByCollectCreat(Long id) {
        PayOrderDetailDO payDetail = getWaitingPayByPayOrder(id);
        payDetail.setExpireTime(null);
        orderDetailMapper.updateById(payDetail);
    }

    @Override
    public void updatePayOrderByCollectRejected(Long id) {
        PayOrderDetailDO payDetail = getWaitingPayByPayOrder(id);
        payDetail.setExpireTime(addTime(Duration.ofHours(1)));
        orderDetailMapper.updateById(payDetail);
    }

    @Override
    public void rollbackPayExpire(Long payOrderId) {
        PayOrderDO payOrderDO = getOrder(payOrderId);
        payOrderDO.setWaitingPay(BigDecimal.ZERO);
        orderMapper.updateById(payOrderDO);
        PayOrderDetailDO payDetail = getWaitingPayByPayOrder(payOrderId);
        payDetail.setStatus(PayorderDetailStatusEnum.CLOSED.getStatus());
        orderDetailMapper.updateById(payDetail);
    }

    /**
     * 同步单个支付单
     *
     * @param order 支付单
     * @return 是否已过期
     */
    private boolean expireOrder(PayOrderDO order) {
        try {
            // 1. 需要先处理关联的支付拓展单，避免错误的过期已支付 or 已退款的订单
            List<PayOrderDetailDO> payOrderDetailDOS = orderDetailMapper.selectListByOrderId(order.getId());
            for (PayOrderDetailDO payOrderDetailDO : payOrderDetailDOS) {
                if (PayorderDetailStatusEnum.isClosed(payOrderDetailDO.getStatus())) {
                    continue;
                }
                // 情况一：校验数据库中的 orderExtension 是不是已支付
                if (PayorderDetailStatusEnum.isOrderSuccess(payOrderDetailDO.getStatus())) {
                    log.error("[expireOrder][order({}) 的 extension({}) 已支付，可能是数据不一致]",
                            order.getId(), payOrderDetailDO.getId());
                    return false;
                }
                // 情况二：调用三方接口，查询支付单状态，是不是已支付/已退款
                PayClient payClient = channelService.getPayClient(payOrderDetailDO.getChannelId());
                if (payClient == null) {
                    log.error("[expireOrder][渠道编号({}) 找不到对应的支付客户端]", payOrderDetailDO.getChannelId());
                    return false;
                }
                PayOrderRespDTO respDTO = payClient.getOrder(payOrderDetailDO.getNo());
//                if (PayOrderStatusRespEnum.isRefund(respDTO.getStatus())) {
//                    // 补充说明：按道理，应该是 WAITING => SUCCESS => REFUND 状态，如果直接 WAITING => REFUND 状态，说明中间丢了过程
//                    // 此时，需要人工介入，手工补齐数据，保持 WAITING => SUCCESS => REFUND 的过程
//                    log.error("[expireOrder][extension({}) 的 PayOrderRespDTO({}) 已退款，可能是回调延迟]",
//                            payOrderDetailDO.getId(), toJsonString(respDTO));
//                    return false;
//                }
                if (PayOrderStatusRespEnum.isSuccess(respDTO.getStatus())) {
                    notifyOrder(payOrderDetailDO.getChannelId(), respDTO);
                    return false;
                }
                // 兜底逻辑：将支付拓展单更新为已关闭
                PayOrderDetailDO updateObj = new PayOrderDetailDO().setStatus(PayOrderStatusEnum.CLOSED.getStatus())
                        .setChannelNotifyData(toJsonString(respDTO));
                if (orderDetailMapper.updateByIdAndStatus(payOrderDetailDO.getId(), PayOrderStatusEnum.WAITING.getStatus(),
                        updateObj) == 0) {
                    log.error("[expireOrder][extension({}) 更新为支付关闭失败]", payOrderDetailDO.getId());
                    return false;
                }
                log.info("[expireOrder][extension({}) 更新为支付关闭成功]", payOrderDetailDO.getId());
            }

            // 2. 都没有上述情况，可以安心更新为已关闭
            PayOrderDO updateObj = new PayOrderDO().setStatus(PayOrderStatusEnum.CLOSED.getStatus());
            if (orderMapper.updateByIdAndStatus(order.getId(), order.getStatus(), updateObj) == 0) {
                log.error("[expireOrder][order({}) 更新为支付关闭失败]", order.getId());
                return false;
            }
            log.info("[expireOrder][order({}) 更新为支付关闭失败]", order.getId());
            return true;
        } catch (Throwable e) {
            log.error("[expireOrder][order({}) 过期订单异常]", order.getId(), e);
            return false;
        }
    }

    /**
     * 获得自身的代理对象，解决 AOP 生效问题
     *
     * @return 自己
     */
    private PayOrderServiceImpl getSelf() {
        return SpringUtil.getBean(getClass());
    }

    private void setExtendPrice(PayOrderDetailDO pay, PayOrderDO order) {
        PayNotifyTypeEnum payType = PayNotifyTypeEnum.getEnum(pay.getPayType());
        switch (payType) {
            case ORDER_DEPOSIT -> {
                if (order.getDeposit().compareTo(BigDecimal.ZERO) > 0) {
                    order.setDepositAdd(order.getDepositAdd().add(pay.getPayPrice()));
                }
                order.setDeposit(order.getDeposit().add(pay.getPayPrice()));
            }
            case ORDER_DELAY -> order.setDelayPrice(order.getDelayPrice().add(pay.getPayPrice()));
            case ORDER_PROCESS -> order.setProcessPrice(order.getProcessPrice().add(pay.getPayPrice()));
            case ORDER_ADDITIONAL -> order.setAdditionalPrice(order.getAdditionalPrice().add(pay.getPayPrice()));
            case ORDER_DEDUCT -> order.setDeductPrice(order.getDeductPrice().add(pay.getPayPrice()));
            case SETTLE_ORDER -> {
                PayOrderDetailDO.ExtendPrice extendPrice = JsonUtils.parseObject(pay.getExtendItem(), PayOrderDetailDO.ExtendPrice.class);
                if (null != extendPrice) {
                    if (null != extendPrice.getDeposit()){
                        order.setDeposit(order.getDeposit().add(extendPrice.getDeposit()));
                        order.setDepositAdd(BigDecimal.ZERO);
                    }
                    if (null != extendPrice.getDelayPrice()){
                        order.setDelayPrice(order.getDelayPrice().add(extendPrice.getDelayPrice()));
                    }
                    if (null != extendPrice.getProcessPrice()){
                        order.setProcessPrice(order.getProcessPrice().add(extendPrice.getProcessPrice()));
                    }
                    if (null != extendPrice.getAdditionalPrice()){
                        order.setAdditionalPrice(order.getAdditionalPrice().add(extendPrice.getAdditionalPrice()));
                    }
                    if (null != extendPrice.getDeductPrice()) {
                        order.setDeductPrice(order.getDeductPrice().add(extendPrice.getDeductPrice()));
                    }
                }

            }
        }
    }

    private String getExtendPrice(PayOrderCreateReqDTO reqDTO) {
        // 结算单类型，创建ExtendPrice对象，所有属性默认为0
        PayOrderDetailDO.ExtendPrice extendPrice = new PayOrderDetailDO.ExtendPrice();
        // 设置reqDTO中的值
        if (null != reqDTO.getDeposit()) {
            extendPrice.setDeposit(reqDTO.getDeposit());
        }
        if (null != reqDTO.getDelayPrice()) {
            extendPrice.setDelayPrice(reqDTO.getDelayPrice());
        }
        if (null != reqDTO.getProcessPrice()) {
            extendPrice.setProcessPrice(reqDTO.getProcessPrice());
        }
        if (null != reqDTO.getAdditionalPrice()) {
            extendPrice.setAdditionalPrice(reqDTO.getAdditionalPrice());
        }
        if (null != reqDTO.getDeductPrice()) {
            extendPrice.setDeductPrice(reqDTO.getDeductPrice());
        }
        return JsonUtils.toJsonString(extendPrice);
    }

    private void autoPay(String channelCode, Long payOrderId, Long payDetailId, String orderNotifyUrl, PayOrderCreateReqDTO reqDTO) {
        PayOrderSubmitReqVO reqVO = new PayOrderSubmitReqVO();
        if (PayChannelEnum.SPEC.getCode().equals(channelCode)) {
            PayCollectOrderDO orderDO = new PayCollectOrderDO();
            orderDO.setOrderNo(reqDTO.getOrderNo());
            orderDO.setPayOrderNo(String.valueOf(payOrderId));
            orderDO.setUserId(reqDTO.getUserId());
            orderDO.setUserName(orderDO.getUserName());
            orderDO.setCollectInfoId(reqDTO.getCollectInfoId());
            orderDO.setAmount(reqDTO.getPrice());
            orderDO.setActualAmount(reqDTO.getPrice());
            orderDO.setFee(BigDecimal.ZERO);
            orderDO.setPayPicUrl(reqDTO.getPayPicUrl());
            orderDO.setRemark("自动结算");
            orderDO.setStatus(1);
            orderDO.setType(PayCollectOrderTypeEnum.PAYMENT.getType());
            orderDO.setAuditAdminId(getLoginUserId());
            orderDO.setAuditAdminName(getLoginUserName());
            orderDO.setAuditReason("自动结算");
            orderDO.setAuditTime(LocalDateTime.now());
            collectOrderService.createCollectOrderAuto(orderDO);

            reqVO.setCheckResult(true);
            reqVO.setPayPicUrl(reqDTO.getPayPicUrl());
        }
        reqVO.setId(payDetailId);
        reqVO.setPayOrderId(payOrderId);
        reqVO.setWalletType(PayWalletTypeEnum.RMB.getType());
        reqVO.setChannelCode(channelCode);
        reqVO.setReturnUrl(orderNotifyUrl);
        Map<String, String> channelExtras = Maps.newHashMapWithExpectedSize(2);
        channelExtras.put(WalletPayClient.USER_ID_KEY, String.valueOf(reqDTO.getUserId()));
        channelExtras.put(WalletPayClient.USER_TYPE_KEY, String.valueOf(UserTypeEnum.MEMBER_PERSON.getValue()));
        reqVO.setChannelExtras(channelExtras);
        submitOrder(reqVO, getClientIP());
    }

}
